/*
 * Copyright (c) 2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.libermundi.theorcs.core.tapestry.services.assets;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import javax.imageio.ImageIO;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.tapestry5.Asset;
import org.apache.tapestry5.internal.services.AssetResourceLocator;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.services.AssetFactory;
import org.libermundi.theorcs.core.exceptions.ImageManipulationException;
import org.libermundi.theorcs.core.util.ImageUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Martin Papy
 *
 */
public class OfsImageCache implements ImageCache {
	private static final Logger logger = LoggerFactory.getLogger(OfsImageCache.class);
	
	private static final String CACHE_DIR="cache";
	
	private static final String EHCACHE_NAME="OfsImageCache";
	
	private FileValidator _validator;
	
	private AssetResourceLocator _resourceLocator;
	
	private AssetFactory _assetFactory;	
	
	private File _basePath; 
	
	private File _baseCachePath;
	
	private Ehcache _ehCache;

	public OfsImageCache(String homeDirPath, FileValidator validator, AssetResourceLocator resourceLocator, AssetFactory assetFactory, CacheManager cacheManager) {
		this._basePath=new File(OfsFilesUtils.computerBaseFilerPath(homeDirPath,""));
		this._baseCachePath = new File(OfsFilesUtils.computerBaseFilerPath(homeDirPath,CACHE_DIR));
		this._validator = validator;
		this._assetFactory = assetFactory;
		this._resourceLocator = resourceLocator;
		this._ehCache = cacheManager.addCacheIfAbsent(EHCACHE_NAME);
	}	

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.tapestry.services.assets.ImageCache#isImage(java.lang.String)
	 */
	@Override
	public boolean isImage(String filename) {
		return _validator.isAllowed(filename,FileType.IMAGE);
	}
	
	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.tapestry.services.assets.ImageCache#isImage(java.io.File)
	 */
	@Override
	public boolean isImage(File file) {
		return isImage(file.getName());
	}
	
	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.tapestry.services.assets.ImageCache#isImage(org.apache.tapestry5.Asset)
	 */
	@Override
	public boolean isImage(Asset asset) {
		return isImage(asset.getResource().getFile());
	}

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.tapestry.services.assets.ImageCache#getImageAssetFromCache(org.apache.tapestry5.Asset, int, int, org.libermundi.theorcs.core.tapestry.components.Image.TransformMode)
	 */
	@Override
	public Asset getImageAssetFromCache(Asset image, int width, int height, ImageTransformMode transform) {
		Asset cachedImage = null;
		if(isImage(image)) {
			String md5 = getHash(image);
			String imageName = image.getResource().getFile();
			String fullPath = computeFullFileCachePath(md5, imageName, width, height, transform);
			String relativePath = CACHE_DIR + OfsFilesUtils.FILE_SEPARATOR + computePartialFileCachePath(md5, imageName, width, height, transform);
			File check = new File(fullPath);
			if(!check.exists()){
				 BufferedImage cache = renderImage(image,width,height,transform);
				 saveImageInCache(cache, fullPath);
			}
			cachedImage = loadAsset(relativePath);
		}
		return cachedImage;
	}


	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.tapestry.services.assets.ImageCache#getImageAssetFromCache(java.io.File, int, int, org.libermundi.theorcs.core.tapestry.components.Image.TransformMode)
	 */
	@Override
	public Asset getImageAssetFromCache(File path, int width, int height, ImageTransformMode transform) {
		return getImageAssetFromCache(getAssetFromFile(path), width, height, transform);
	}

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.tapestry.services.assets.ImageCache#removeFromCache(java.io.File)
	 */
	@Override
	public void removeFromCache(File fileToDelete) {
		if(fileToDelete.exists()){
			String md5 = getHash(fileToDelete);
			File delete = new File(computeFullDirCachePath(md5));
			if(delete.exists()){
				FileUtils.deleteQuietly(delete);
			}			
		}
	}

	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.tapestry.services.assets.ImageCache#getBasePath()
	 */
	@Override
	public String getBasePath() {
		return _baseCachePath.getAbsolutePath();
	}
	
	private void saveImageInCache(BufferedImage image, String fullPath) {
		if(image != null) {
			File saveTo = new File(fullPath);
			assert(_validator.isAllowed(saveTo.getName(),FileType.IMAGE));
			OfsFilesUtils.sanitiseDirPath(fullPath);
			String ext = FilenameUtils.getExtension(saveTo.getName());
			try {
				ImageIO.write(image, ext, saveTo);
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	private String computeFullDirCachePath(String directory){
		File fullDir = new File(getBasePath(),computePartialDirCachePath(directory));
		return fullDir.getAbsolutePath();
	}
	
	private static String computePartialDirCachePath(String directory){
		StringBuilder path = new StringBuilder(OfsFilesUtils.computeSubDirectory(directory));
			path.append(FilenameUtils.getBaseName(directory))
				.append(OfsFilesUtils.FILE_SEPARATOR);
		
		return path.toString();
	}

	private String computeFullFileCachePath(String directory, String imageName, int width, int height, ImageTransformMode transform){
		return getBasePath() + OfsFilesUtils.FILE_SEPARATOR + computePartialFileCachePath(directory, imageName, width, height, transform);
	}

	private static String computePartialFileCachePath(String directory, String imageName, int width, int height, ImageTransformMode transform){
		StringBuilder path = new StringBuilder(computePartialDirCachePath(directory));
			path.append(transform.toString().toLowerCase())
				.append("-")
				.append(width)
				.append("x")
				.append(height)
				.append(".")
				.append(FilenameUtils.getExtension(imageName).toLowerCase());
			
		return path.toString();
	}

	private static BufferedImage renderImage(Asset image, int width, int height, ImageTransformMode transform){
		if(image.getResource().exists()) {
			URL imageURL = image.getResource().toURL();
			BufferedImage renderedImage = null; 
			try {
				switch (transform) {
					case FORCE: 
						renderedImage = ImageUtils.resizeImage(imageURL, width, height);
						break;
					case PROPORTIONAL:
						if(width > 0) {
							renderedImage = ImageUtils.resizeImageByWidth(imageURL, width);
						} else {
							renderedImage = ImageUtils.resizeImageByHeight(imageURL, height);
						}
						break;
					case CROP:
						renderedImage = ImageUtils.cropImage(imageURL, width, height);
						break;
					case THUMBNAIL:
						renderedImage = ImageUtils.thumbnail(imageURL, width, height);
						break;
					case FIT:
						renderedImage = ImageUtils.fitIn(imageURL,width,height);
					default:
						throw new UnsupportedOperationException();
				}
				return renderedImage;
			} catch (ImageManipulationException e) {
				throw new RuntimeException();
			}
		}
		return null;
	}
	
	/**
	 * @param path
	 * @return
	 */
	private Asset getAssetFromFile(File path) {
		String relativePath = getRelativePathFromFullPath(path.getAbsolutePath());
		return loadAsset(relativePath);
	}

	/**
	 * @param absolutePath
	 * @return
	 */
	private String getRelativePathFromFullPath(String absolutePath) {
		return absolutePath.replace(_basePath.getAbsolutePath(), "");
	}
	
	private Asset loadAsset(String relativePath){
		Resource r;
		try {
			r = _resourceLocator.findClasspathResourceForPath(relativePath);
		} catch (IOException e) {
			logger.error("Could not get the cached image for : " + relativePath);
			return null;
		}
		return  _assetFactory.createAsset(r);
	}
	
	private String getHash(Asset asset){
		try {
			return getHash(asset.getResource().getPath(), asset.getResource().openStream());
		} catch (IOException e) {
			return null;
		}
	}

	private String getHash(File file){
		try {
			return getHash(file.getAbsolutePath(), new FileInputStream(file));
		} catch (IOException e) {
			return null;
		}
	}

	private String getHash(String path, InputStream is) {
		//Check if the Hash result is in the cache ?
		Element elem = _ehCache.get(path);
		if (elem == null) {
			try {
				elem = new Element(path, DigestUtils.md5Hex(is));
				_ehCache.put(elem);
			} catch (IOException e) {
				return null;
			}
		}
		return (String)elem.getObjectValue();
	}

}
